﻿/*********************************************************************************************************************
VanguardDocumentation, Procedure SplitEntityName

© 2014 Erik Ackerholm

This file is part of SqlVanguard.

SqlVanguard is free software: you can redistribute it and/or modify it under the terms of the GNU General Public 
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later 
version.

SqlVanguard is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied 
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with SqlVanguard. If not, see 
<http://www.gnu.org/licenses/>.
*********************************************************************************************************************/

if
	not exists (
		select *
		from sys.schemas as s
			inner join sys.objects as o on (s.schema_id = o.schema_id)
		where s.name = 'VanguardDocumentation' and o.[type] = 'P'
			and o.name = 'SplitEntityName'
	)
begin
	declare @SqlCommand as nvarchar(max);
	set @SqlCommand = '
		create procedure VanguardDocumentation.SplitEntityName
		as raiserror (''Procedure SplitEntityName is not implemented yet!'', 11, 1);
	';
	execute (@SqlCommand);
end;
GO

alter procedure VanguardDocumentation.SplitEntityName
	/*****************************************************************************************************************
	Splits a dot-separated name into its parts.
	
	Depending on the number of parts either
		<empty/null> => @Level0Name = null, @Level1Name = null, @Level2Name = null
		One => @Level0Name = One, @Level1Name = null, @Level2Name = null
		One.Two => @Level0Name = One, @Level1Name = Two, @Level2Name = null
		One.Two.Three => @Level0Name = One, @Level1Name = Two, @Level2Name = Three
	
	Illegal names should cause an exception.
	*****************************************************************************************************************/
	
	@FullEntityName                nvarchar(512),
		/* Dot-separated name that the procedure should split. */
	@Level0Name                    nvarchar(128)                    output,
		/* First part of name. Is null for empty input, meaning properties are get/set at the database level. */
	@Level1Name                    nvarchar(128)                    output,
		/* Second part of name. Is null for one-part names (schemas and suchlike). */
	@Level2Name                    nvarchar(128)                    output
		/* Third part of name. Is null for one-part or two-part names (schemas, tables and suchlike). */
	
	with execute as owner
as
begin
	
	set nocount on;
	
	declare @ErrorNo as integer;
	declare @ErrorMsg as nvarchar(1536);
	declare @ErrorLine as integer;
	declare @ErrorState as integer;
	
	begin try;
		
		-- Empty name means database level. Returns at once for these names to be able to catch errors in parsename,
		-- which will cause null output (same as with null or empty input).
		if
			@FullEntityName is null or @FullEntityName = ''
		begin
			return;
		end;
		
		-- Throw error if trying a four-part name (longer names will cause null output and are caught later)
		if
			parsename(@FullEntityName, 4) is not null
		begin
			raiserror (740901, 16, 1, @FullEntityName);
			return;
		end;
		
		-- Parsename happily accepts names beginning and ending with a dot, but I think this is sloppy and confusing.
		-- Therefore checks specifically for this and throws errors if dots are found.
		if
			@FullEntityName like '.%'
		begin
			raiserror (740902, 16, 2, @FullEntityName, 'first');
			return;
		end
		else if
			@FullEntityName like '%.'
		begin
			raiserror (740902, 16, 3, @FullEntityName, 'last');
			return;
		end;
		
		-- Now parsing into three parts. If a part is missing, parsename will return null.
		set @Level0Name = parsename(@FullEntityName, 3);
		set @Level1Name = parsename(@FullEntityName, 2);
		set @Level2Name = parsename(@FullEntityName, 1);
		
		-- Parsename is sets the latter parts of a name if not a three-part name. We want to prioritize the first 
		-- parts. Therefore we have to move level 1 and 2 upward if higher levels are missing
		if
			@Level0Name is null and @Level1Name is null
		begin
			set @Level0Name = @Level2Name;
			set @Level2Name = null;
		end
		else if
			@Level0Name is null
		begin
			set @Level0Name = @Level1Name;
			set @Level1Name = @Level2Name;
			set @Level2Name = null;
		end;
		
		-- Level 0 should only be null if @FullEntityName is empty (or null), but this condition is checked at the top
		-- and therefore this must mean an error has occured (parsename returns null for errors).
		if
			@Level0Name is null
		begin
			raiserror (740903, 16, 4, @FullEntityName)
			return;
		end;
		
		-- If we only found the first and last part this means a double dot was part of the input name. We must have 
		-- all parts to be able to match a name (no defaults) and thus throws an error for this condition.
		if
			@Level1Name is null and @Level2Name is not null
		begin
			raiserror (740902, 16, 5, @FullEntityName, 'middle');
			return;
		end;
		
	end try
	begin catch;
		
		set @ErrorNo = error_number();
		set @ErrorMsg = left(error_message(), 1536);
		set @ErrorLine = error_line();
		set @ErrorState = error_state();
		
		raiserror (740900, 16, @ErrorState, 'SplitEntityName', @ErrorNo, @ErrorLine, @ErrorMsg);
		
	end catch;
	
end;
GO




